#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<strings.h>
#include<netinet/in.h>
#include<functional>
#include<arpa/inet.h>
#include<cstdio>
#include<string.h>
#include"Log.hpp"
using func_t = function<string(const string&)>;

Log log; 

using namespace std;

enum
{
    SOCK_ERR = 1,
    BIND_ERR = 2    
};

//用来设置缺省值
const uint16_t defaultport = 8080;
const string defaultip = "123.249.103.51";
const int size = 1024;

class UdpServer
{
public://一般用户都不喜欢用4字节的整形表示ip地址。 用户都是用的点分十进制字符串风格的ip地址。 
    UdpServer(uint16_t port = defaultport, string ip = defaultip)
        :port_(port) 
        ,ip_(ip)
        ,isrunning_(false)
    {}

    void init()
    {
        //创建udp套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            log(Fatal, "socket create error, sockfd: ", sockfd_);
            exit(1);
        }
        log(Info, "socket create success, sockfd: %d", sockfd_);
        
        //2.绑定端口号

        //2.1创建套接字
        struct sockaddr_in local;   //结构是什么？ ——一个叫做sin_port, 一个sin_addr, sin_port. 这两个都是 

        //2.2初始化套接字
        bzero(&local, sizeof(local));
        //2.3将套接字进行赋值
        local.sin_family = AF_INET; //sin_family表明我们自己的结构体类型    
        //主机序列转网络序列， 有接口
        local.sin_port = htons(port_);     //保证我们的端口号是网络字节序列， 因为该端口号是要给对方发送的。
        local.sin_addr.s_addr = inet_addr(ip_.c_str());       //string -> uint32_t, 如何快速的将ip转为字符串, 就是使用inet_addr
        
        // local.sin_addr.s_addr = INADDR_ANY;       //string -> uint32_t, 如何快速的将ip转为字符串, 就是使用inet_addr
        
        //2.4. bind 绑定
        //我们的端口号ip地址， 有没有向操作系统的套接字去设置。 答案是没有， 截止到现在， 我们的套接字local还是在栈上面。 地址空间的用户级栈上。 
        
        if ((bind(sockfd_, (const struct sockaddr*)&local, sizeof(local))) < 0) 
        {
            log(Fatal, "bind error, errno %d, error string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }

        log(Info, "bind success, error: %d, error string: %s", errno, strerror(errno));

    }   

    void run(func_t func)
    {   
        isrunning_ = true;
        char inbuffer[size];

        while (isrunning_)
        {
            //能够从udp中读取数据
            struct sockaddr_in client;
            socklen_t clilen = sizeof(client);
            //从客户端读取数据  
            ssize_t n = recvfrom(sockfd_,  inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &clilen);
            
            if (n < 0)
            {
                log(Waring, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            //
            inbuffer[n] = 0;
            string info = inbuffer;
            string echo_string = func(info);  //回调函数， 对代码进行分层。 


            sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, clilen);
        }

    }

    ~UdpServer()
    {
        if (sockfd_ > 0) close(sockfd_);
    }


private:
    int sockfd_;  //网络文件描述符
    uint16_t port_;   //端口号, 表明服务器的端口号
    string ip_;       //ip地址

    //我们的网络服务器是一直能够一直运行的
    bool isrunning_;
};